{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[1, 4, 2, 6, 6],\n",
       "       [0, 7, 2, 1, 9],\n",
       "       [8, 3, 5, 8, 5],\n",
       "       [4, 8, 0, 9, 3]])"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import numpy as np\n",
    "\n",
    "a = np.random.randint(0, 10, size=(4,5))\n",
    "a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "86"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.sum(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[18 26 18 24]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([17, 23, 12, 22, 12])"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(np.sum(a, axis=1))\n",
    "np.sum(a, axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([1.1, 1.1, 1.1, ..., 1.1, 1.1, 1.1])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "b = np.ones(1000000, dtype=np.float64) * 1.1\n",
    "b"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1099999.9999999977\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "1099999.9999999977"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(np.sum(b))\n",
    "np.sum(b, dtype=np.double)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3.6 5.2 3.6 4.8]\n",
      "np.mean(b) =  1.0999999999999976\n",
      "np.mean(b, dtype=np.double) =  1.0999999999999976\n"
     ]
    }
   ],
   "source": [
    "print(np.mean(a, axis=1))\n",
    "print(\"np.mean(b) = \", np.mean(b))\n",
    "print(\"np.mean(b, dtype=np.double) = \", np.mean(b, dtype=np.double))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "13\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(np.argmax(a))\n",
    "a.ravel()[2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(0, 2)\n",
      "1\n"
     ]
    }
   ],
   "source": [
    "idx = np.unravel_index(2, a.shape)\n",
    "print(idx)\n",
    "print(a[idx])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 4, 3, 1])"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "idx = np.argmax(a, axis=1)\n",
    "idx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[7 8 9 9]\n"
     ]
    }
   ],
   "source": [
    "print(a[range(a.shape[0]), idx])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 1, 3, 7, 7],\n",
       "       [1, 5, 5, 7, 8],\n",
       "       [0, 0, 3, 6, 9],\n",
       "       [1, 3, 5, 6, 9]])"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.sort(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 1, 0, 3, 0],\n",
       "       [5, 6, 1, 3, 1],\n",
       "       [5, 7, 5, 7, 3],\n",
       "       [7, 9, 6, 9, 8]])"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.sort(a, axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[4, 2, 3, 0, 1],\n",
       "       [1, 0, 2, 3, 4],\n",
       "       [0, 2, 4, 1, 3],\n",
       "       [4, 3, 0, 2, 1]])"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "idx = np.argsort(a)\n",
    "idx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0],\n",
       "       [1],\n",
       "       [2],\n",
       "       [3]])"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x, _ = np.ogrid[:a.shape[0],:a.shape[1]]\n",
    "x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([[0, 1, 3, 7, 7],\n",
       "       [1, 5, 5, 7, 8],\n",
       "       [0, 0, 3, 6, 9],\n",
       "       [1, 3, 5, 6, 9]])"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a[x, idx]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([5. , 6.5, 3. , 5. , 2. ])"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.median(a, axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'numpy.poly1d'>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([ 1.      ,  0.515625,  0.125   , -0.078125,  0.      ])"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a = np.array([1.0, 0, -2, 1])\n",
    "p = np.poly1d(a)\n",
    "print(type(p))\n",
    "p(np.linspace(0, 1, 5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "poly1d([ 1.,  0., -4.,  2.])"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p + [-2, 1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "poly1d([ 1.,  0., -4.,  2.,  4., -4.,  1.])"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p*p"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(poly1d([ 1., -1., -1.]), poly1d([2.]))"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p / [1, 1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p == np.poly1d([1., -1., -1.]) * [1,1] + 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "poly1d([ 3.,  0., -2.])"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.deriv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "poly1d([ 0.25,  0.  , -1.  ,  1.  ,  0.  ])"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.integ()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.integ().deriv() == p"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([-1.61803399,  1.        ,  0.61803399])"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "r = np.roots(p)\n",
    "r"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([2.33146835e-15, 4.44089210e-16, 1.11022302e-16])"
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p(r)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 1.00000000e+00, -7.77156117e-16, -2.00000000e+00,  1.00000000e+00])"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.poly(r)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([1, 2, 1])"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.polymul([1,1],[1,1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "poly 3:  [-1.45057734e-01  0.00000000e+00  9.88787919e-01 -1.74339711e-17]\n",
      "max error of order 3: 0.009028097444729988\n",
      "poly 5:  [ 7.57410176e-03  1.04997632e-16 -1.65826716e-01 -1.87533180e-16\n",
      "  9.99771274e-01 -1.46306751e-17]\n",
      "max error of order 5: 0.000160422346623168\n",
      "poly 7:  [-1.84469511e-04 -3.74977117e-16  8.30950763e-03  1.39225872e-15\n",
      " -1.66651672e-01 -9.63744045e-16  9.99997485e-01  2.94368419e-17]\n",
      "max error of order 7: 1.5773189405710042e-06\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<matplotlib.legend.Legend at 0x7f100c2c4bd0>"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as pl\n",
    "\n",
    "fig, ax = pl.subplots()\n",
    "\n",
    "x = np.linspace(-np.pi/2, np.pi/2, 10000)\n",
    "y = np.sin(x)\n",
    "\n",
    "for deg in [3,5,7]:\n",
    "    a = np.polyfit(x, y, deg)\n",
    "    error = np.abs(np.polyval(a, x) - y)\n",
    "    print(\"poly %d: \" % deg, a)\n",
    "    print(\"max error of order %d:\" % deg, np.max(error))\n",
    "    ax.plot(x, error, label=\"Poly \" + str(deg))\n",
    "ax.set_xlabel('x label')\n",
    "ax.set_ylabel('y label')\n",
    "ax.set_title('poly fit')\n",
    "ax.legend()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([9, 8, 7, 6, 5, 5, 6, 7, 8, 9])"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = np.arange(10)\n",
    "np.where(x<5, 9-x, x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0,  0,  0,  0,  0,  0,  0, 14, 16, 18])"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.where(x>6, 2*x, 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {},
   "outputs": [],
   "source": [
    "def triangle_wave(x, c, c0, hc):\n",
    "    x = x - x.astype(np.int)\n",
    "    return np.where(x>=c, 0, np.where(x<c0, x/c0*hc, (c-x)/(c-c0)*hc))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [],
   "source": [
    "def triangle_wave2(x, c, c0, hc):\n",
    "    x = x - x.astype(np.int)\n",
    "    return np.select([x>=c, x<c0, True], [0, x/c0*hc, (c-x)/(c-c0)*hc])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [],
   "source": [
    "def triangle_wave3(x,c, c0, hc):\n",
    "    x = x - x.astype(np.int)\n",
    "    return np.piecewise(x, \n",
    "                       [x >=c, c<c0],\n",
    "                       [0, # x>=c\n",
    "                       lambda x: x/c0*hc, # x<c0,\n",
    "                       lambda x: (c-x)/(c-c0)*hc]) # else"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([4, 0, 0, 0, 0, 1, 3, 3, 0, 3])"
      ]
     },
     "execution_count": 44,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a = np.random.randint(0, 5, 10)\n",
    "a"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0, 1, 3, 4])"
      ]
     },
     "execution_count": 45,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.unique(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0 1 3 4]\n",
      "[1 5 6 0]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([0, 1, 3, 4])"
      ]
     },
     "execution_count": 49,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x, idx = np.unique(a, return_index=True)\n",
    "print(x)\n",
    "print(idx)\n",
    "a[idx]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([3, 0, 0, 0, 0, 1, 2, 2, 0, 2])"
      ]
     },
     "execution_count": 50,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x, ridx = np.unique(a, return_inverse=True)\n",
    "ridx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "all(x[ridx] == a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([5, 1, 0, 3, 1])"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.bincount(a)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([1.3, 1.6, 0.6])"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = np.array([0, 1,2,2,1,1,0])\n",
    "w = np.array([0.1, 0.3, 0.2, 0.4, 0.5, 0.8, 1.2])\n",
    "np.bincount(x, w)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.65      , 0.53333333, 0.3       ])"
      ]
     },
     "execution_count": 54,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.bincount(x, w)/np.bincount(x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([5, 0, 1, 0, 0, 0, 0, 3, 0, 1]),\n",
       " array([0. , 0.4, 0.8, 1.2, 1.6, 2. , 2.4, 2.8, 3.2, 3.6, 4. ]))"
      ]
     },
     "execution_count": 56,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.histogram(a, bins=10, range=None, weights=None)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([20, 24, 22, 16, 18]), array([0. , 0.2, 0.4, 0.6, 0.8, 1. ]))"
      ]
     },
     "execution_count": 57,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a = np.random.rand(100)\n",
    "np.histogram(a, bins=5, range=(0,1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(array([44, 38, 18]), array([0. , 0.4, 0.8, 1. ]))"
      ]
     },
     "execution_count": 58,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.histogram(a, bins=[0, 0.4, 0.8, 1.0], range=(0, 1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
